/* Allow access to a raw mixing buffer */

#include "t_pcm_audio_device.h"

#include <stdio.h> //snprintf()
#include "directx.h"

static void SetDSerror(const char *function, int code);
static int CreateSecondary(LPDIRECTSOUND sndObj, HWND focus,
    LPDIRECTSOUNDBUFFER *sndbuf, WAVEFORMATEX *wavefmt, quint32 chunksize);

static void My_CalculateAudioSpec(T_PCM_AudioDeviceSpec *spec)
{
    switch (spec->nBitsPerSample) {
    case 8: //AUDIO_U8:
            spec->nSilence = 0x80;
            break;
        default:
            spec->nSilence = 0x00;
            break;
    }
    spec->nSampleSize = (spec->nBitsPerSample)/8;
    spec->nSampleSize *= spec->nChannels;
    spec->nSampleSize *= spec->nSampleUnit;
}

class T_PCM_AudioDevicePrivate
{
public:
    LPDIRECTSOUND m_sound;
    LPDIRECTSOUNDBUFFER m_mixbuf;
    int m_NUM_BUFFERS;
    int m_mixlen;
    int m_silence;
    DWORD m_lastchunk;
    quint8 *m_locked_buf;
    explicit T_PCM_AudioDevicePrivate()
        : m_test1(777)
        , m_sound(0)
        , m_mixbuf(0)
        , m_NUM_BUFFERS(0)
        , m_mixlen(0)
        , m_silence(0)
        , m_lastchunk(0)
        , m_locked_buf(0)
    {
        qDebug() << "T_PCM_AudioDevicePrivate::T_PCM_AudioDevicePrivate()";
    }
    int m_test1;
    void fn_test1()
    {
        QObjectPrivate *x;
        qDebug() << "[m_test1]" <<  m_test1;
    }
};

T_PCM_AudioDevice::T_PCM_AudioDevice(QObject *parent)
    :QObject(parent)
    ,d_ptr(new T_PCM_AudioDevicePrivate())
    , enabled(1)
    , paused(1)
    , opened(0)
{
    qDebug() << "T_PCM_AudioDevice::T_PCM_AudioDevice()";
    ////d_ptr = new T_PCM_AudioDevicePrivate();
    Q_D(T_PCM_AudioDevice);
    d->fn_test1();
    d->m_test1 = 123;
    d->fn_test1();
}

T_PCM_AudioDevice::~T_PCM_AudioDevice()
{
    delete d_ptr;
}

int T_PCM_AudioDevice::OpenAudio(T_PCM_AudioDeviceSpec *spec)
{
    Q_D(T_PCM_AudioDevice);

    HRESULT      result;
    WAVEFORMATEX waveformat;

    /* Set basic WAVE format parameters */
    memset(&waveformat, 0, sizeof(waveformat));
    waveformat.wFormatTag = WAVE_FORMAT_PCM;

    /* Determine the audio parameters from the AudioSpec */
    switch ( spec->nBitsPerSample ) {
        case 8:
            /* Unsigned 8 bit audio data */
            ////spec->format = AUDIO_U8;
            d->m_silence = 0x80;
            waveformat.wBitsPerSample = 8;
            break;
        case 16:
            /* Signed 16 bit audio data */
            ////spec->format = AUDIO_S16;
            d->m_silence = 0x00;
            waveformat.wBitsPerSample = 16;
            break;
        default:
#if 0x0
            XXX_SetError("Unsupported audio format");
#endif
            return(-1);
    }
    /* Update the fragment size as size in bytes */
    My_CalculateAudioSpec(spec);
    this->m_spec = *spec;

    waveformat.nChannels = spec->nChannels;
    waveformat.nSamplesPerSec = spec->nSamplesPerSec;
    waveformat.nBlockAlign =
        waveformat.nChannels * (waveformat.wBitsPerSample/8);
    waveformat.nAvgBytesPerSec =
        waveformat.nSamplesPerSec * waveformat.nBlockAlign;

    /* Open the audio device */
    result = DirectSoundCreate(NULL, &d->m_sound, NULL);
    if ( result != DS_OK ) {
        SetDSerror("DirectSoundCreate", result);
        return(-1);
    }

    /* Create the audio buffer to which we write */
    d->m_NUM_BUFFERS = -1;
    d->m_NUM_BUFFERS = CreateSecondary(
            d->m_sound,
            (HWND)0,
            &d->m_mixbuf,
            &waveformat,
            spec->nSampleSize);
    if ( d->m_NUM_BUFFERS < 0 ) {
        return(-1);
    }

    /* The buffer will auto-start playing in DX5_WaitAudio() */
    d->m_lastchunk = 0;
    d->m_mixlen = spec->nSampleSize;

    this->opened = 1;
    return(0);
}

quint8 *T_PCM_AudioDevice::GetAudioBuf()
{
    Q_D(T_PCM_AudioDevice);

    DWORD   cursor, junk;
    HRESULT result;
    DWORD   rawlen;

    /* Figure out which blocks to fill next */
    d->m_locked_buf = NULL;
    result = IDirectSoundBuffer_GetCurrentPosition(d->m_mixbuf, &junk, &cursor);
    if ( result == DSERR_BUFFERLOST ) {
        IDirectSoundBuffer_Restore(d->m_mixbuf);
        result = IDirectSoundBuffer_GetCurrentPosition(d->m_mixbuf, &junk, &cursor);
    }
    if ( result != DS_OK ) {
        SetDSerror("DirectSound GetCurrentPosition", result);
        return(NULL);
    }
    cursor /= d->m_mixlen;
    d->m_lastchunk = cursor;
    cursor = (cursor+1)%d->m_NUM_BUFFERS;
    cursor *= d->m_mixlen;

    /* Lock the audio buffer */
    result = IDirectSoundBuffer_Lock(d->m_mixbuf, cursor, d->m_mixlen,
                (LPVOID *)&d->m_locked_buf, &rawlen, NULL, &junk, 0);
    if ( result == DSERR_BUFFERLOST ) {
        IDirectSoundBuffer_Restore(d->m_mixbuf);
        result = IDirectSoundBuffer_Lock(d->m_mixbuf, cursor, d->m_mixlen,
                (LPVOID *)&d->m_locked_buf, &rawlen, NULL, &junk, 0);
    }
    if ( result != DS_OK ) {
        SetDSerror("DirectSound Lock", result);
        return(NULL);
    }
    return(d->m_locked_buf);
}

void T_PCM_AudioDevice::PlayAudio()
{
    Q_D(T_PCM_AudioDevice);
    /* Unlock the buffer, allowing it to play */
    if ( d->m_locked_buf ) {
        IDirectSoundBuffer_Unlock(d->m_mixbuf, d->m_locked_buf, d->m_mixlen, NULL, 0);
    }
}

void T_PCM_AudioDevice::WaitAudio()
{
    Q_D(T_PCM_AudioDevice);
    DWORD status;
    DWORD cursor, junk;
    HRESULT result;

    /* Semi-busy wait, since we have no way of getting play notification
       on a primary mixing buffer located in hardware (DirectX 5.0)
    */
    result = IDirectSoundBuffer_GetCurrentPosition(d->m_mixbuf, &junk, &cursor);
    if ( result != DS_OK ) {
        if ( result == DSERR_BUFFERLOST ) {
            IDirectSoundBuffer_Restore(d->m_mixbuf);
        }
        return;
    }

    while ( (cursor/d->m_mixlen) == d->m_lastchunk ) {
        /* FIXME: find out how much time is left and sleep that long */
        Sleep(1); //Win32 API

        /* Try to restore a lost sound buffer */
        IDirectSoundBuffer_GetStatus(d->m_mixbuf, &status);
        if ( (status&DSBSTATUS_BUFFERLOST) ) {
            IDirectSoundBuffer_Restore(d->m_mixbuf);
            IDirectSoundBuffer_GetStatus(d->m_mixbuf, &status);
            if ( (status&DSBSTATUS_BUFFERLOST) ) {
                break;
            }
        }
        if ( ! (status&DSBSTATUS_PLAYING) ) {
            result = IDirectSoundBuffer_Play(d->m_mixbuf, 0, 0, DSBPLAY_LOOPING);
            if ( result == DS_OK ) {
                continue;
            }
            return;
        }

        /* Find out where we are playing */
        result = IDirectSoundBuffer_GetCurrentPosition(d->m_mixbuf, &junk, &cursor);
        if ( result != DS_OK ) {
            SetDSerror("DirectSound GetCurrentPosition", result);
            return;
        }
    }
}

void T_PCM_AudioDevice::WaitDone()
{
    Q_D(T_PCM_AudioDevice);
    quint8 *stream;

    /* Wait for the playing chunk to finish */
    stream = this->GetAudioBuf();
    if ( stream != NULL ) {
        memset(stream, d->m_silence, d->m_mixlen);
        this->PlayAudio();
    }
    this->WaitAudio();

    /* Stop the looping sound buffer */
    IDirectSoundBuffer_Stop(d->m_mixbuf);
}

void T_PCM_AudioDevice::CloseAudio()
{
    Q_D(T_PCM_AudioDevice);
    if ( d->m_sound != NULL ) {
        if ( d->m_mixbuf != NULL ) {
            /* Clean up the audio buffer */
            IDirectSoundBuffer_Release(d->m_mixbuf);
            d->m_mixbuf = NULL;
        }
        IDirectSound_Release(d->m_sound);
        d->m_sound = NULL;
    }
}

static void SetDSerror(const char *function, int code)
{
	static const char *error;
	static char  errbuf[1024];

	errbuf[0] = 0;
	switch (code) {
		case E_NOINTERFACE:
			error = 
		"Unsupported interface\n-- Is DirectX 5.0 or later installed?";
			break;
		case DSERR_ALLOCATED:
			error = "Audio device in use";
			break;
		case DSERR_BADFORMAT:
			error = "Unsupported audio format";
			break;
		case DSERR_BUFFERLOST:
			error = "Mixing buffer was lost";
			break;
		case DSERR_CONTROLUNAVAIL:
			error = "Control requested is not available";
			break;
		case DSERR_INVALIDCALL:
			error = "Invalid call for the current state";
			break;
		case DSERR_INVALIDPARAM:
			error = "Invalid parameter";
			break;
		case DSERR_NODRIVER:
			error = "No audio device found";
			break;
		case DSERR_OUTOFMEMORY:
			error = "Out of memory";
			break;
		case DSERR_PRIOLEVELNEEDED:
			error = "Caller doesn't have priority";
			break;
		case DSERR_UNSUPPORTED:
			error = "Function not supported";
			break;
		default:
            snprintf(errbuf, sizeof(errbuf),
			         "%s: Unknown DirectSound error: 0x%x",
								function, code);
			break;
	}
	if ( ! errbuf[0] ) {
        snprintf(errbuf, sizeof(errbuf), "%s: %s", function, error);
	}
    ////fprintf(stderr, "%s\n", errbuf);
	return;
}

/* This function tries to create a secondary audio buffer, and returns the
   number of audio chunks available in the created buffer.
*/
static int CreateSecondary(LPDIRECTSOUND sndObj, HWND focus,
    LPDIRECTSOUNDBUFFER *sndbuf, WAVEFORMATEX *wavefmt, quint32 chunksize)
{
	const int numchunks = 8;
	HRESULT result;
	DSBUFFERDESC format;
	LPVOID pvAudioPtr1, pvAudioPtr2;
	DWORD  dwAudioBytes1, dwAudioBytes2;

	/* Try to set primary mixing privileges */
	if ( focus ) {
		result = IDirectSound_SetCooperativeLevel(sndObj,
					focus, DSSCL_PRIORITY);
	} else {
		result = IDirectSound_SetCooperativeLevel(sndObj,
					GetDesktopWindow(), DSSCL_NORMAL);
	}
	if ( result != DS_OK ) {
		return(-1);
	}

	/* Try to create the secondary buffer */
    memset(&format, 0, sizeof(format));
	format.dwSize = sizeof(format);
	format.dwFlags = DSBCAPS_GETCURRENTPOSITION2;
	if ( ! focus ) {
		format.dwFlags |= DSBCAPS_GLOBALFOCUS;
	} else {
		format.dwFlags |= DSBCAPS_STICKYFOCUS;
	}
	format.dwBufferBytes = numchunks*chunksize;
	if ( (format.dwBufferBytes < DSBSIZE_MIN) ||
	     (format.dwBufferBytes > DSBSIZE_MAX) ) {
#if 0x0
        XXX_SetError("Sound buffer size must be between %d and %d",
				DSBSIZE_MIN/numchunks, DSBSIZE_MAX/numchunks);
#endif
		return(-1);
	}
	format.dwReserved = 0;
	format.lpwfxFormat = wavefmt;
	result = IDirectSound_CreateSoundBuffer(sndObj, &format, sndbuf, NULL);
	if ( result != DS_OK ) {
		SetDSerror("DirectSound CreateSoundBuffer", result);
		return(-1);
	}
	IDirectSoundBuffer_SetFormat(*sndbuf, wavefmt);

	/* Silence the initial audio buffer */
	result = IDirectSoundBuffer_Lock(*sndbuf, 0, format.dwBufferBytes,
	                                 (LPVOID *)&pvAudioPtr1, &dwAudioBytes1,
	                                 (LPVOID *)&pvAudioPtr2, &dwAudioBytes2,
	                                 DSBLOCK_ENTIREBUFFER);
	if ( result == DS_OK ) {
		if ( wavefmt->wBitsPerSample == 8 ) {
            memset(pvAudioPtr1, 0x80, dwAudioBytes1);
		} else {
            memset(pvAudioPtr1, 0x00, dwAudioBytes1);
		}
		IDirectSoundBuffer_Unlock(*sndbuf,
		                          (LPVOID)pvAudioPtr1, dwAudioBytes1,
		                          (LPVOID)pvAudioPtr2, dwAudioBytes2);
	}

	/* We're ready to go */
	return(numchunks);
}
